package com.zhx.common.chat.service.impl;

import com.zhx.common.chat.dao.GroupMemberDao;
import com.zhx.common.chat.dao.RoomDao;
import com.zhx.common.chat.dao.RoomFriendDao;
import com.zhx.common.chat.dao.RoomGroupDao;
import com.zhx.common.chat.domain.entity.GroupMember;
import com.zhx.common.chat.domain.entity.Room;
import com.zhx.common.chat.domain.entity.RoomFriend;
import com.zhx.common.chat.domain.entity.RoomGroup;
import com.zhx.common.chat.domain.enums.GroupRoleEnum;
import com.zhx.common.chat.domain.enums.HotFlagEnum;
import com.zhx.common.chat.domain.enums.RoomTypeEnum;
import com.zhx.common.chat.domain.vo.request.ChatMessageMemberReq;
import com.zhx.common.chat.domain.vo.request.GroupAddReq;
import com.zhx.common.chat.domain.vo.request.member.MemberAddReq;
import com.zhx.common.chat.domain.vo.request.member.MemberDelReq;
import com.zhx.common.chat.domain.vo.request.member.MemberReq;
import com.zhx.common.chat.domain.vo.response.ChatMemberListResp;
import com.zhx.common.chat.domain.vo.response.MemberResp;
import com.zhx.common.chat.service.ChatService;
import com.zhx.common.chat.service.RoomService;
import com.zhx.common.chat.service.adapter.ChatAdapter;
import com.zhx.common.chat.service.adapter.MemberAdapter;
import com.zhx.common.chat.service.adapter.RoomAdapter;
import com.zhx.common.chat.service.cache.GroupMemberCache;
import com.zhx.common.chat.service.cache.RoomCache;
import com.zhx.common.chat.service.cache.RoomGroupCache;
import com.zhx.common.common.annotation.RedissonLock;
import com.zhx.common.common.domain.enums.NormalOrNoEnum;
import com.zhx.common.common.domain.vo.resp.CursorPageBaseResp;
import com.zhx.common.common.event.GroupMemberAddEvent;
import com.zhx.common.common.exception.GroupErrorEnum;
import com.zhx.common.common.util.AssertUtil;
import com.zhx.common.user.dao.UserDao;
import com.zhx.common.user.domain.entity.User;
import com.zhx.common.user.domain.enums.RoleEnum;
import com.zhx.common.user.service.IRoleService;
import com.zhx.common.user.service.cache.UserCache;
import com.zhx.common.user.service.cache.UserInfoCache;
import com.zhx.common.user.service.impl.PushService;
import com.zhx.common.websocket.domain.vo.resp.ChatMemberResp;
import com.zhx.common.websocket.domain.vo.resp.WSBaseResp;
import com.zhx.common.websocket.domain.vo.resp.WSMemberChange;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class RoomServiceImpl implements RoomService {
    @Resource
    private RoomFriendDao roomFriendDao;
    @Resource
    private RoomGroupCache roomGroupCache;
    @Resource
    private RoomCache roomCache;
    @Resource
    private UserCache userCache;
    @Resource
    private GroupMemberDao groupMemberDao;
    @Resource
    private UserDao userDao;
    @Resource
    private ChatService chatService;
    @Resource
    private UserInfoCache userInfoCache;
    @Resource
    private IRoleService iRoleService;
    @Resource
    private PushService pushService;
    @Resource
    private GroupMemberCache groupMemberCache;
    @Resource
    private RoomDao roomDao;
    @Resource
    private RoomGroupDao roomGroupDao;
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    /**
     * 创建好友房间。该方法首先校验传入的好友ID列表的非空性和长度（必须为2），然后根据好友ID列表生成房间密钥。
     * 如果根据该密钥在数据库中找到了已存在的房间，且该房间状态为非正常状态，则恢复该房间；
     * 如果未找到对应房间，则新建一个好友房间，并创建对应的房间关系数据。
     *
     * @param uidList 好友ID列表，不能为空且列表长度必须为2。
     * @return 创建或恢复后的房间好友关系对象。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public RoomFriend createFriendRoom(List<Long> uidList) {
        // 校验好友ID列表非空和长度是否为2
        AssertUtil.isNotEmpty(uidList, "房间创建失败，好友数量不对");
        AssertUtil.equal(uidList.size(), 2, "房间创建失败，好友数量不对");
        // 生成房间密钥
        String key = ChatAdapter.generateRoomKey(uidList);

        // 尝试根据房间密钥获取房间好友关系，判断是否需要恢复或新建房间
        RoomFriend roomFriend = roomFriendDao.getByKey(key);
        if (Objects.nonNull(roomFriend)) { // 如果存在房间，判断是否需要恢复
            if (Objects.equals(roomFriend.getStatus(), NormalOrNoEnum.NOT_NORMAL.getStatus())) {
                // 房间状态为非正常，进行恢复操作
                roomFriendDao.restoreRoom(roomFriend.getId());
            }
        } else { // 不存在对应房间，新建房间及房间好友关系
            Room room = createRoom(RoomTypeEnum.FRIEND);
            roomFriend = createFriendRoom(room.getId(), uidList);
        }
        return roomFriend;
    }


    @Override
    public RoomFriend getFriendRoom(Long uid, Long friendUid) {
        String key = ChatAdapter.generateRoomKey(Arrays.asList(uid, friendUid));
        return roomFriendDao.getByKey(key);
    }

    @Override
    public MemberResp getGroupDetail(Long uid, long roomId) {
        RoomGroup roomGroup = roomGroupCache.get(roomId);
        Room room = roomCache.get(roomId);
        AssertUtil.isNotEmpty(roomGroup, "roomId有误");
        //获取在线人数
        Long onlineNum;
        if (isHotGroup(room)) { //热点群从redis取人数
            onlineNum = userCache.getOnlineNum();
        } else {
            List<Long> memberUidList = groupMemberDao.getMemberUidList(roomGroup.getId());
            onlineNum = userDao.getOnlineCount(memberUidList).longValue();
        }
        return MemberResp.builder()
                .roomId(roomId)
                .groupName(roomGroup.getName())
                .avatar(roomGroup.getAvatar())
                .onlineNum(onlineNum)
                .build();
    }

    @Override
    public CursorPageBaseResp<ChatMemberResp> getMemberPage(MemberReq request) {
        Room room = roomCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(room, "房间号有误");
        List<Long> memberUidList;
        if (isHotGroup(room)) {//全员群展示所有用户
            memberUidList = null;
        } else {//只展示房间内的群成员
            RoomGroup roomGroup = roomGroupCache.get(request.getRoomId());
            memberUidList = groupMemberDao.getMemberUidList(roomGroup.getId());
        }
        return chatService.getMemberPage(request, memberUidList);
    }

    @Override
    @Cacheable(cacheNames = "member", key = "'memberList.' + #request.roomId")
    public List<ChatMemberListResp> getMemberList(ChatMessageMemberReq request) {
        Room room = roomCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(room, "房间号有误");
        if (isHotGroup(room)) { //全员群展示所有用户100名
            List<User> memberList = userDao.getMemberList();
            return MemberAdapter.buildMemberList(memberList);
        } else {
            RoomGroup roomGroup = roomGroupCache.get(request.getRoomId());
            List<Long> memberUidList = groupMemberDao.getMemberUidList(roomGroup.getId());
            Map<Long, User> batch = userInfoCache.getBatch(memberUidList);
            return MemberAdapter.buildMemberList(batch);
        }
    }

    /**
     * 删除群组成员。
     *
     * @param uid     当前操作用户的ID。
     * @param request 包含要删除的用户ID和房间ID的请求对象。
     *                校验当前用户是否有权从指定房间中删除成员，若有权则执行删除操作，并向房间内所有成员发送成员删除的WebSocket消息。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delMember(Long uid, MemberDelReq request) {
        // 从缓存中获取房间对象
        Room room = roomCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(room, "房间号有误");

        // 从缓存中获取房间组对象
        RoomGroup roomGroup = roomGroupCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(roomGroup, "房间号有误");

        // 校验当前用户是否在房间组中
        GroupMember self = groupMemberDao.getMember(roomGroup.getId(), uid);
        AssertUtil.isNotEmpty(self, GroupErrorEnum.USER_NOT_IN_GROUP);

        Long removeUid = request.getUid();
        // 校验被删除用户是否为群主，防止群主被错误删除
        AssertUtil.isFalse(groupMemberDao.isLord(roomGroup.getId(), removeUid), GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE);

        // 如果被删除用户是管理员，校验操作用户是否为群主，防止非群主删除管理员
        if (groupMemberDao.isManager(roomGroup.getId(), removeUid)) {
            Boolean isLord = groupMemberDao.isLord(roomGroup.getId(), uid);
            AssertUtil.isTrue(isLord, GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE);
        }

        // 校验当前用户是否有权限删除成员
        AssertUtil.isTrue(hasPower(self), GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE);

        // 从数据库中获取要删除的成员信息，再次校验其存在性
        GroupMember member = groupMemberDao.getMember(roomGroup.getId(), removeUid);
        AssertUtil.isNotEmpty(member, "用户已经移除");

        // 执行成员删除操作
        groupMemberDao.removeById(member.getId());

        // 获取房间内所有成员的UID列表，用于发送WebSocket消息
        List<Long> memberUidList = groupMemberCache.getMemberUidList(roomGroup.getRoomId());
        // 构建成员删除的WebSocket消息
        WSBaseResp<WSMemberChange> ws = MemberAdapter.buildMemberRemoveWS(roomGroup.getRoomId(), member.getUid());
        // 向房间内所有成员发送消息
        pushService.sendPushMsg(ws, memberUidList);
        // 从缓存中移除成员UID列表
        groupMemberCache.evictMemberUidList(room.getId());
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public RoomGroup createGroupRoom(Long uid) {
        // 获取用户创建的群组列表，确保用户没有创建过群组
        List<GroupMember> selfGroup = groupMemberDao.getSelfGroup(uid);
        AssertUtil.isEmpty(selfGroup, "每个人只能创建一个群");

        // 从缓存中获取用户信息
        User user = userInfoCache.get(uid);

        // 创建一个群组类型的房间
        Room room = ChatAdapter.buildRoom(RoomTypeEnum.GROUP);
        roomDao.save(room);

        // 创建群组，并保存到数据库
        RoomGroup roomGroup = ChatAdapter.buildGroupRoom(user, room.getId());
        roomGroupDao.save(roomGroup);

        // 保存群组主
        GroupMember leader = GroupMember.builder()
                .role(GroupRoleEnum.LEADER.getType())
                .groupId(roomGroup.getId())
                .uid(uid)
                .build();
        groupMemberDao.save(leader);

        return roomGroup;
    }

    @Override
    @Transactional
    public Long addGroup(Long uid, GroupAddReq request) {
        RoomGroup roomGroup = createGroupRoom(uid);
        // 批量保存群成员
        List<GroupMember> groupMembers = RoomAdapter.buildGroupMemberBatch(request.getUidList(), roomGroup.getId());
        groupMemberDao.saveBatch(groupMembers);
        // 发送邀请加群消息==》触发每个人的会话
        applicationEventPublisher.publishEvent(new GroupMemberAddEvent(this, roomGroup, groupMembers, uid));
        return roomGroup.getRoomId();
    }

    /**
     * 向房间添加成员。
     *
     * @param uid 用户ID，表示请求添加成员的用户ID。
     * @param request 包含房间ID和待添加成员ID列表的请求对象。
     *
     * 该方法首先从缓存中获取房间和房间组信息，验证房间存在性和是否为全员群。然后，验证请求用户是否为群成员，
     * 并获取已存在的成员列表，过滤出待添加的成员。最后，批量添加这些成员到群组中，并发布成员添加事件。
     * 使用Redisson锁确保并发安全，并使用事务确保操作的原子性。
     */
    @Override
    @RedissonLock(key = "#request.roomId")
    @Transactional(rollbackFor = Exception.class)
    public void addMember(Long uid, MemberAddReq request) {
        // 从缓存中获取房间信息，并校验房间是否存在
        Room room = roomCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(room, "房间号有误");

        // 验证是否为全员群，全员群无需邀请好友
        AssertUtil.isFalse(isHotGroup(room), "全员群无需邀请好友");

        // 从缓存中获取房间组信息，并校验其存在性
        RoomGroup roomGroup = roomGroupCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(roomGroup, "房间号有误");

        // 验证请求用户是否为当前群组的成员
        GroupMember self = groupMemberDao.getMember(roomGroup.getId(), uid);
        AssertUtil.isNotEmpty(self, "您不是群成员");

        // 获取已存在于群组中的成员ID列表
        List<Long> memberBatch = groupMemberDao.getMemberBatch(roomGroup.getId(), request.getUidList());
        Set<Long> existUid = new HashSet<>(memberBatch);

        // 筛选出待添加的成员ID列表
        List<Long> waitAddUidList = request.getUidList().stream().filter(a -> !existUid.contains(a)).distinct().collect(Collectors.toList());

        // 如果没有待添加成员，则直接返回
        if (CollectionUtils.isEmpty(waitAddUidList)) {
            return;
        }

        // 批量构建待添加的群组成员对象
        List<GroupMember> groupMembers = MemberAdapter.buildMemberAdd(roomGroup.getId(), waitAddUidList);

        // 批量保存待添加的群组成员
        groupMemberDao.saveBatch(groupMembers);

        // 发布群组成员添加事件
        applicationEventPublisher.publishEvent(new GroupMemberAddEvent(this, roomGroup, groupMembers, uid));
    }

    @Override
    public void disableFriendRoom(List<Long> uidList) {
        AssertUtil.isNotEmpty(uidList, "房间创建失败，好友数量不对");
        AssertUtil.equal(uidList.size(), 2, "房间创建失败，好友数量不对");
        String key = ChatAdapter.generateRoomKey(uidList);
        roomFriendDao.disableRoom(key);
    }

    /**
     * 检查指定的群组成员是否具有权力。
     * <p>
     * 通过比较群组成员的角色和用户ID对应的权限来判断成员是否具有权力。
     * 权力来源可以是群组的领导者、管理员或者系统管理员。
     * </p>
     *
     * @param self 表示当前群组成员的对象。
     * @return 如果当前成员是领导者、管理员，或者在系统中具有管理员权限，则返回true；否则返回false。
     */
    private boolean hasPower(GroupMember self) {
        // 检查群组成员的角色是否为群主或管理员
        // 如果不是，检查该成员是否具有系统管理员权限
        return Objects.equals(self.getRole(), GroupRoleEnum.LEADER.getType())
                || Objects.equals(self.getRole(), GroupRoleEnum.MANAGER.getType())
                || iRoleService.hasPower(self.getUid(), RoleEnum.ADMIN);
    }

    private Room createRoom(RoomTypeEnum typeEnum) {
        Room insert = ChatAdapter.buildRoom(typeEnum);
        roomDao.save(insert);
        return insert;
    }

    private RoomFriend createFriendRoom(Long roomId, List<Long> uidList) {
        RoomFriend insert = ChatAdapter.buildFriendRoom(roomId, uidList);
        roomFriendDao.save(insert);
        return insert;
    }

    private boolean isHotGroup(Room room) {
        return HotFlagEnum.YES.getType().equals(room.getHotFlag());
    }
}
